// internal/service/preview_service.go
package service

import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/disintegration/imaging"
	"github.com/unidoc/unipdf/v3/extractor"
	pdf "github.com/unidoc/unipdf/v3/model"

	"github.com/jfastfiler/internal/dao"
	"github.com/jfastfiler/internal/model"
	"github.com/jfastfiler/internal/util"
)

type PreviewService struct {
	fileDAO         *dao.FileMetadataDAO
	previewBaseDir  string
	tempDir         string
	maxWorkers      int
	imageQuality    int
	documentTimeout int
	libreofficePath string
	storageDir      string
}

func NewPreviewService(fileDAO *dao.FileMetadataDAO, previewBaseDir, tempDir, storageDir string, 
	maxWorkers, imageQuality, documentTimeout int, libreofficePath string) *PreviewService {
	
	os.MkdirAll(previewBaseDir, 0755)
	os.MkdirAll(tempDir, 0755)
	os.MkdirAll(storageDir, 0755)

	return &PreviewService{
		fileDAO:         fileDAO,
		previewBaseDir:  previewBaseDir,
		tempDir:         tempDir,
		storageDir:      storageDir,
		maxWorkers:      maxWorkers,
		imageQuality:    imageQuality,
		documentTimeout: documentTimeout,
		libreofficePath: libreofficePath,
	}
}

type PreviewRequest struct {
	FileID  int    `json:"file_id"`
	Width   int    `json:"width"`   // 预览图宽度
	Height  int    `json:"height"`  // 预览图高度
	Page    int    `json:"page"`    // 文档页码
	Quality int    `json:"quality"` // 图片质量
}

type PreviewResponse struct {
	PreviewURL string `json:"preview_url"`
	Width      int    `json:"width"`
	Height     int    `json:"height"`
	PageCount  int    `json:"page_count,omitempty"`
	Success    bool   `json:"success"`
	Message    string `json:"message,omitempty"`
}

type PreviewStatus struct {
	FileID       int    `json:"file_id"`
	Status       string `json:"status"` // pending, processing, completed, failed
	Progress     int    `json:"progress"`
	PreviewPath  string `json:"preview_path,omitempty"`
	ErrorMessage string `json:"error_message,omitempty"`
}

func (s *PreviewService) GeneratePreview(req *PreviewRequest) (*PreviewResponse, error) {
	file, err := s.fileDAO.GetByID(req.FileID)
	if err != nil {
		return nil, errors.New("文件不存在")
	}

	// 检查文件是否可预览
	if !s.isPreviewable(file) {
		return nil, errors.New("该文件类型不支持预览")
	}

	// 设置默认值
	if req.Width <= 0 {
		req.Width = 800
	}
	if req.Height <= 0 {
		req.Height = 600
	}
	if req.Quality <= 0 || req.Quality > 100 {
		req.Quality = s.imageQuality
	}

	// 检查是否已有预览
	if file.PreviewStatus == 2 && file.PreviewPath != "" {
		previewPath := filepath.Join(s.previewBaseDir, file.PreviewPath)
		if _, err := os.Stat(previewPath); err == nil {
			return &PreviewResponse{
				PreviewURL: "/api/v1/preview/" + file.PreviewPath,
				Width:      req.Width,
				Height:     req.Height,
				Success:    true,
			}, nil
		}
	}

	// 更新预览状态为生成中
	s.fileDAO.UpdatePreviewStatus(file.ID, 1, "")

	// 异步生成预览
	go s.generatePreviewAsync(file, req)

	return &PreviewResponse{
		Success: true,
		Message: "预览生成任务已提交，请稍后刷新查看",
	}, nil
}

func (s *PreviewService) generatePreviewAsync(file *model.FileMetadata, req *PreviewRequest) {
	var previewPath string
	var pageCount int
	var err error

	defer func() {
		status := 2 // 已完成
		if err != nil {
			status = 3 // 失败
		}
		s.fileDAO.UpdatePreviewStatus(file.ID, status, previewPath)
	}()

	filePath := filepath.Join(s.storageDir, file.Path)

	switch strings.ToLower(file.FileExt) {
	case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif":
		previewPath, err = s.generateImagePreview(file, filePath, req.Width, req.Height, req.Quality)
	case ".pdf":
		previewPath, pageCount, err = s.generatePDFPreview(file, filePath, req.Width, req.Height, req.Page, req.Quality)
	case ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".rtf":
		previewPath, pageCount, err = s.generateDocumentPreview(file, filePath, req.Width, req.Height, req.Page, req.Quality)
	default:
		err = errors.New("不支持预览的文件类型: " + file.FileExt)
	}

	if err != nil {
		fmt.Printf("生成预览失败: %v\n", err)
	} else {
		fmt.Printf("预览生成成功: %s, 页数: %d\n", previewPath, pageCount)
	}
}

func (s *PreviewService) generateImagePreview(file *model.FileMetadata, filePath string, width, height, quality int) (string, error) {
	// 生成预览文件名
	previewFileName := fmt.Sprintf("%d_%dx%d_q%d.jpg", file.ID, width, height, quality)
	previewPath := filepath.Join(s.previewBaseDir, "images", previewFileName)

	// 检查是否已存在预览
	if _, err := os.Stat(previewPath); err == nil {
		return "images/" + previewFileName, nil
	}

	// 确保目录存在
	os.MkdirAll(filepath.Dir(previewPath), 0755)

	// 打开原图
	img, err := imaging.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("打开图片失败: %v", err)
	}

	// 调整大小（保持宽高比）
	img = imaging.Fit(img, width, height, imaging.Lanczos)

	// 保存预览图
	var buf bytes.Buffer
	err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
	if err != nil {
		return "", fmt.Errorf("保存预览图失败: %v", err)
	}

	// 写入文件
	err = os.WriteFile(previewPath, buf.Bytes(), 0644)
	if err != nil {
		return "", fmt.Errorf("写入预览文件失败: %v", err)
	}

	return "images/" + previewFileName, nil
}

func (s *PreviewService) generatePDFPreview(file *model.FileMetadata, filePath string, width, height, pageNum, quality int) (string, error) {
	// 打开PDF文件
	f, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("打开PDF文件失败: %v", err)
	}
	defer f.Close()

	pdfReader, err := pdf.NewPdfReader(f)
	if err != nil {
		return "", fmt.Errorf("读取PDF失败: %v", err)
	}

	// 获取总页数
	numPages, err := pdfReader.GetNumPages()
	if err != nil {
		return "", fmt.Errorf("获取PDF页数失败: %v", err)
	}

	if pageNum < 1 || pageNum > int(numPages) {
		pageNum = 1
	}

	// 生成预览文件名
	previewFileName := fmt.Sprintf("%d_p%d_%dx%d_q%d.jpg", file.ID, pageNum, width, height, quality)
	previewPath := filepath.Join(s.previewBaseDir, "pdfs", previewFileName)

	// 检查是否已存在预览
	if _, err := os.Stat(previewPath); err == nil {
		return "pdfs/" + previewFileName, int(numPages), nil
	}

	// 确保目录存在
	os.MkdirAll(filepath.Dir(previewPath), 0755)

	// 获取指定页面
	page, err := pdfReader.GetPage(pageNum)
	if err != nil {
		return "", fmt.Errorf("获取PDF页面失败: %v", err)
	}

	// 这里简化处理，实际应该使用更专业的PDF渲染
	// 可以使用外部工具如ImageMagick或ghostscript
	previewPath, err = s.generatePDFPreviewWithExternalTool(filePath, previewPath, pageNum, width, height, quality)
	if err != nil {
		return "", err
	}

	return "pdfs/" + previewFileName, int(numPages), nil
}

func (s *PreviewService) generatePDFPreviewWithExternalTool(pdfPath, outputPath string, pageNum, width, height, quality int) (string, error) {
	// 尝试使用ImageMagick的convert命令
	cmd := exec.Command("convert", 
		"-density", "150",
		fmt.Sprintf("%s[%d]", pdfPath, pageNum-1),
		"-resize", fmt.Sprintf("%dx%d", width, height),
		"-quality", fmt.Sprintf("%d", quality),
		outputPath)

	output, err := cmd.CombinedOutput()
	if err != nil {
		// 如果ImageMagick不可用，尝试使用ghostscript
		cmd = exec.Command("gs", 
			"-dNOPAUSE", "-dBATCH", "-dSAFER",
			"-sDEVICE=jpeg", "-dJPEGQ=95", "-r150",
			fmt.Sprintf("-dFirstPage=%d", pageNum),
			fmt.Sprintf("-dLastPage=%d", pageNum),
			fmt.Sprintf("-sOutputFile=%s", outputPath),
			pdfPath)

		output, err = cmd.CombinedOutput()
		if err != nil {
			return "", fmt.Errorf("PDF预览生成失败: %v, 输出: %s", err, string(output))
		}
	}

	return outputPath, nil
}

func (s *PreviewService) generateDocumentPreview(file *model.FileMetadata, filePath string, width, height, pageNum, quality int) (string, error) {
	if s.libreofficePath == "" {
		return "", errors.New("LibreOffice未配置，无法生成文档预览")
	}

	// 生成临时PDF路径
	tempPDF := filepath.Join(s.tempDir, fmt.Sprintf("%d_temp.pdf", file.ID))
	defer os.Remove(tempPDF) // 清理临时文件

	// 使用LibreOffice将文档转换为PDF
	cmd := exec.Command(s.libreofficePath,
		"--headless",
		"--convert-to", "pdf",
		"--outdir", s.tempDir,
		filePath)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("文档转换失败: %v, 输出: %s", err, string(output))
	}

	// 检查生成的PDF文件
	if _, err := os.Stat(tempPDF); err != nil {
		// 尝试查找LibreOffice生成的PDF文件
		baseName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
		tempPDF = filepath.Join(s.tempDir, baseName+".pdf")
		if _, err := os.Stat(tempPDF); err != nil {
			return "", errors.New("PDF转换文件未找到")
		}
	}

	// 现在使用PDF预览生成
	pdfFile := &model.FileMetadata{
		ID:      file.ID,
		Path:    tempPDF,
		FileExt: ".pdf",
	}

	previewPath, pageCount, err := s.generatePDFPreview(pdfFile, tempPDF, width, height, pageNum, quality)
	if err != nil {
		return "", err
	}

	return previewPath, pageCount, nil
}

func (s *PreviewService) GetPreviewStatus(fileID int) (*PreviewStatus, error) {
	file, err := s.fileDAO.GetByID(fileID)
	if err != nil {
		return nil, errors.New("文件不存在")
	}

	status := &PreviewStatus{
		FileID: fileID,
	}

	switch file.PreviewStatus {
	case 0:
		status.Status = "pending"
	case 1:
		status.Status = "processing"
		status.Progress = 50
	case 2:
		status.Status = "completed"
		status.Progress = 100
		status.PreviewPath = file.PreviewPath
	case 3:
		status.Status = "failed"
		status.ErrorMessage = "预览生成失败"
	}

	return status, nil
}

func (s *PreviewService) isPreviewable(file *model.FileMetadata) bool {
	previewableExts := map[string]bool{
		// 图片格式
		".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
		".bmp": true, ".webp": true, ".tiff": true, ".tif": true,
		// 文档格式
		".pdf": true, ".doc": true, ".docx": true, ".xls": true,
		".xlsx": true, ".ppt": true, ".pptx": true, ".txt": true,
		".rtf": true,
	}

	return previewableExts[strings.ToLower(file.FileExt)]
}

func (s *PreviewService) ExtractText(fileID int) (string, error) {
	file, err := s.fileDAO.GetByID(fileID)
	if err != nil {
		return "", errors.New("文件不存在")
	}

	filePath := filepath.Join(s.storageDir, file.Path)

	switch strings.ToLower(file.FileExt) {
	case ".pdf":
		return s.extractTextFromPDF(filePath)
	case ".txt":
		return s.extractTextFromTXT(filePath)
	case ".doc", ".docx":
		return s.extractTextFromDOC(filePath)
	default:
		return "", errors.New("不支持文本提取的文件类型")
	}
}

func (s *PreviewService) extractTextFromPDF(filePath string) (string, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer f.Close()

	pdfReader, err := pdf.NewPdfReader(f)
	if err != nil {
		return "", err
	}

	numPages, err := pdfReader.GetNumPages()
	if err != nil {
		return "", err
	}

	var textBuilder strings.Builder

	for i := 1; i <= int(numPages); i++ {
		page, err := pdfReader.GetPage(i)
		if err != nil {
			continue
		}

		ex, err := extractor.New(page)
		if err != nil {
			continue
		}

		text, err := ex.ExtractText()
		if err == nil && text != "" {
			textBuilder.WriteString(text)
			textBuilder.WriteString("\n\n")
		}
	}

	return textBuilder.String(), nil
}

func (s *PreviewService) extractTextFromTXT(filePath string) (string, error) {
	content, err := os.ReadFile(filePath)
	if err != nil {
		return "", err
	}
	return string(content), nil
}

func (s *PreviewService) extractTextFromDOC(filePath string) (string, error) {
	// 使用外部工具（如antiword或catdoc）提取文本
	cmd := exec.Command("antiword", filePath)
	output, err := cmd.Output()
	if err != nil {
		// 尝试使用catdoc
		cmd = exec.Command("catdoc", filePath)
		output, err = cmd.Output()
		if err != nil {
			return "", errors.New("文档文本提取失败，请安装antiword或catdoc")
		}
	}
	return string(output), nil
}

func (s *PreviewService) CleanupOldPreviews(maxAge time.Duration) error {
	cutoffTime := time.Now().Add(-maxAge)
	
	// 清理图片预览
	imageDir := filepath.Join(s.previewBaseDir, "images")
	s.cleanupDirectory(imageDir, cutoffTime)
	
	// 清理PDF预览
	pdfDir := filepath.Join(s.previewBaseDir, "pdfs")
	s.cleanupDirectory(pdfDir, cutoffTime)
	
	// 清理临时文件
	s.cleanupDirectory(s.tempDir, cutoffTime)
	
	return nil
}

func (s *PreviewService) cleanupDirectory(dir string, cutoffTime time.Time) {
	files, err := os.ReadDir(dir)
	if err != nil {
		return
	}

	for _, file := range files {
		info, err := file.Info()
		if err != nil {
			continue
		}

		if info.ModTime().Before(cutoffTime) {
			os.Remove(filepath.Join(dir, file.Name()))
		}
	}
}